package org.rency.spring.cloud.boot.mvc.configuration;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jndi.JndiObjectFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.util.Assert;

import javax.naming.NamingException;
import javax.sql.DataSource;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Properties;

/**
 * ${todo}
 *
 * @author: Created by user_rcy@163.com on 2016/8/23.
 * @version: V1.0.0
 * @since JDK 1.8
 */
@Order(1)
@Configuration
@EnableTransactionManagement
public class JdbcDataSourceFactoryBean implements EnvironmentAware {

    private final Logger logger = LoggerFactory.getLogger(DataBaseConfiguration.class);

    private final String FILE_PATH_PREFIX_CLASSPATH = "classpath:";
    private final String FILE_PATH_PREFIX_FILE = "file:";

    private final String DATASOURCE_CONFIG_PREFIX = "restx.persist.ds.";
    private final String DATASOURCE_TYPE = "type";
    private final String DATASOURCE_TYPE_JDBC = "jdbc";
    private final String DATASOURCE_TYPE_JNDI = "jndi";
    private final String DATASOURCE_CONFIG_LOCATION = "config";
    private final String DATASOURCE_JNDI_NAME = "jndi";


    private final String MYBATIS_CONFIG_PREFIX = "mybatis.";
    private final String MYBATIS_CONFIG_LOCATION = "config";
    private final String MYBATIS_CONFIG_TYPEALIASESPACKAGE = "typeAliasesPackage";
    private final String MYBATIS_CONFIG_MAPPERLOCATIONS = "mapperLocations";
    private final String MYBATIS_CONFIG_BASEPACKAGE = "basePackage";

    private final String SESSION_FACTORY_BEAN_NAME = "sqlSessionFactory";


    //数据源
    private RelaxedPropertyResolver dsConfigResolver;
    //Mybatis配置
    private RelaxedPropertyResolver mybatisPropertyResolver;
    //数据源类型
    private String dataSourceType;
    //数据源属性配置文件路径
    private String dsConfigLocation;

    @Override
    public void setEnvironment(Environment environment) {
        this.dsConfigResolver = new RelaxedPropertyResolver(environment,DATASOURCE_CONFIG_PREFIX);
        this.mybatisPropertyResolver = new RelaxedPropertyResolver(environment,MYBATIS_CONFIG_PREFIX);
        Assert.notNull(dsConfigResolver,"DataSource configuration is empty!");
        Assert.notNull(mybatisPropertyResolver,"Mybatis configuration is empty!");

        this.dataSourceType = dsConfigResolver.getProperty(DATASOURCE_TYPE);
        if(this.dataSourceType.equals(DATASOURCE_TYPE_JDBC)){
            this.dsConfigLocation = dsConfigResolver.getProperty(DATASOURCE_CONFIG_LOCATION);
            Assert.isTrue(StringUtils.isNotBlank(this.dsConfigLocation),"DataSource configuration properties file location is null!");
        }
    }

    @Bean(name = "dataSource",destroyMethod = "close", initMethod="init")
    @Primary
    public DataSource dataSource(){
        logger.info("Init DataSource, DataSource Type:{}",dataSourceType);
        DataSource dataSource = null;
        if(this.dataSourceType.equals(DATASOURCE_TYPE_JDBC)){
            dataSource = this.getJdbcDataSource();
        }else if(this.dataSourceType.equals(DATASOURCE_TYPE_JNDI)){
            dataSource = this.getJndiDataSource();
        }else{
            logger.error("DataSource type is not support,{}.",this.dataSourceType);
            throw new RuntimeException("DataSource type not support!");
        }
        logger.info("Init DataSource Finished,{}.",dataSource);
        return dataSource;
    }

    private DataSource getJdbcDataSource(){
        Properties dataSourceProperties = new Properties();
        try {
            if(this.dsConfigLocation.startsWith(FILE_PATH_PREFIX_CLASSPATH)){
                InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(this.dsConfigLocation.substring(FILE_PATH_PREFIX_CLASSPATH.length()));
                dataSourceProperties.load(is);
            }else if(this.dsConfigLocation.startsWith(FILE_PATH_PREFIX_FILE)){
                File file = new File(this.dsConfigLocation.substring(FILE_PATH_PREFIX_FILE.length()));
                dataSourceProperties.load(new FileInputStream(file));
            }else{
                logger.error("DataSource configuration properties file path is not support,{}",this.dsConfigLocation);
                throw new RuntimeException("DataSource configuration properties file path is not support!");
            }
        } catch (Exception e) {
            logger.error("Load dataSource configuration properties failed, file:{}.",this.dsConfigLocation, e);
            throw new RuntimeException("Load dataSource configuration properties failed.", e);
        }
        logger.debug("Create DataSource:{}.",dataSourceProperties);
        DataSource dataSource = null;
        try {
            dataSource = DruidDataSourceFactory.createDataSource(dataSourceProperties);
            dataSource.getConnection();
        } catch (Exception e) {
            logger.error("Create DataSource Failed.",e);
            throw new RuntimeException("Create DataSource Failed!");
        }
        return dataSource;
    }

    private DataSource getJndiDataSource(){
        JndiObjectFactoryBean factory = new JndiObjectFactoryBean();
        String jndiName = dsConfigResolver.getProperty(DATASOURCE_JNDI_NAME);
        logger.debug("Setting DataSource With JNDI[{}].",jndiName);
        factory.setJndiName(jndiName);
        try {
            factory.afterPropertiesSet();
        } catch (NamingException e) {
            logger.error("Lookup DataSource JNDI Failed,{}.",jndiName, e);
            throw new RuntimeException(e);
        }
        return (DataSource) factory.getObject();
    }

    @Bean
    public PlatformTransactionManager txManager() {
        return new DataSourceTransactionManager(dataSource());
    }

    @Bean(name = SESSION_FACTORY_BEAN_NAME)
    public SqlSessionFactory sqlSessionFactoryBean() throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource());
        sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(mybatisPropertyResolver.getProperty(MYBATIS_CONFIG_LOCATION)));
        sessionFactory.setTypeAliasesPackage(mybatisPropertyResolver.getProperty(MYBATIS_CONFIG_TYPEALIASESPACKAGE));

        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sessionFactory.setMapperLocations(resolver.getResources(mybatisPropertyResolver.getProperty(MYBATIS_CONFIG_MAPPERLOCATIONS)));

        return sessionFactory.getObject();
    }

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer() throws Exception {
        MapperScannerConfigurer scanner = new MapperScannerConfigurer();
        scanner.setBasePackage(mybatisPropertyResolver.getProperty(MYBATIS_CONFIG_BASEPACKAGE));
        scanner.setSqlSessionFactoryBeanName(SESSION_FACTORY_BEAN_NAME);
        return scanner;
    }

}
